home *** CD-ROM | disk | FTP | other *** search
/ Enter 2006 September / Enter 09 2006.iso / Internet / SpamExperts Home 1.1 / SpamExperts Home.exe / lib / spamexperts.modules / spambayes / tokenizer.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2006-07-14  |  23.2 KB  |  700 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.4)
  3.  
  4. '''Module to tokenize email messages for spam filtering.'''
  5. from __future__ import generators
  6. import email
  7. import email.Message as email
  8. import email.Header as email
  9. import email.Utils as email
  10. import email.Errors as email
  11. import re
  12. import math
  13. import time
  14. import os
  15. import binascii
  16. import urlparse
  17. import urllib
  18.  
  19. try:
  20.     Set = set
  21. except NameError:
  22.     
  23.     try:
  24.         from sets import Set
  25.     except ImportError:
  26.         from spambayes.compatsets import Set
  27.  
  28.  
  29. from spambayes import classifier
  30. from spambayes.Options import options
  31. from spambayes.mboxutils import get_message
  32.  
  33. try:
  34.     (True, False)
  35. except NameError:
  36.     (True, False) = (1, 0)
  37.  
  38. from encodings.aliases import aliases
  39. if not aliases.has_key('ansi_x3_4_1968'):
  40.     aliases['ansi_x3_4_1968'] = 'ascii'
  41.  
  42. del aliases
  43.  
  44. def textparts(msg):
  45.     """Return a set of all msg parts with content maintype 'text'."""
  46.     return Set(filter((lambda part: part.get_content_maintype() == 'text'), msg.walk()))
  47.  
  48.  
  49. def octetparts(msg):
  50.     """Return a set of all msg parts with type 'application/octet-stream'."""
  51.     return Set(filter((lambda part: part.get_content_type() == 'application/octet-stream'), msg.walk()))
  52.  
  53. has_highbit_char = re.compile('[\\x80-\\xff]').search
  54. html_re = re.compile('\n    <\n    (?![\\s<>])  # e.g., don\'t match \'a < b\' or \'<<<\' or \'i<<5\' or \'a<>b\'\n    # guessing that other tags are usually "short"\n    [^>]{0,256} # search for the end \'>\', but don\'t run wild\n    >\n', re.VERBOSE | re.DOTALL)
  55. received_host_re = re.compile('from ([a-z0-9._-]+[a-z])[)\\s]')
  56. received_ip_re = re.compile('[[(]((\\d{1,3}\\.?){4})[])]')
  57. message_id_re = re.compile('\\s*<[^@]+@([^>]+)>\\s*')
  58. subject_word_re = re.compile('[\\w\\x80-\\xff$.%]+')
  59. punctuation_run_re = re.compile('\\W+')
  60. fname_sep_re = re.compile('[/\\\\:]')
  61.  
  62. def crack_filename(fname):
  63.     yield 'fname:' + fname
  64.     components = fname_sep_re.split(fname)
  65.     morethan1 = len(components) > 1
  66.     for component in components:
  67.         if morethan1:
  68.             yield 'fname comp:' + component
  69.         
  70.         pieces = urlsep_re.split(component)
  71.         if len(pieces) > 1:
  72.             for piece in pieces:
  73.                 yield 'fname piece:' + piece
  74.             
  75.     
  76.  
  77.  
  78. def tokenize_word(word, _len = len, maxword = options[('Tokenizer', 'skip_max_word_size')]):
  79.     n = _len(word)
  80.     if n <= n:
  81.         pass
  82.     elif n <= maxword:
  83.         yield word
  84.     elif n >= 3:
  85.         if n < 40 and '.' in word and word.count('@') == 1:
  86.             (p1, p2) = word.split('@')
  87.             yield 'email name:' + p1
  88.             yield 'email addr:' + p2
  89.         elif options[('Tokenizer', 'generate_long_skips')]:
  90.             yield 'skip:%c %d' % (word[0], (n // 10) * 10)
  91.         
  92.         if has_highbit_char(word):
  93.             hicount = 0
  94.             for i in map(ord, word):
  95.                 if i >= 128:
  96.                     hicount += 1
  97.                     continue
  98.             
  99.             yield '8bit%%:%d' % round(hicount * 100.0 / len(word))
  100.         
  101.     
  102.  
  103. non_ascii_translate_tab = [
  104.     '?'] * 256
  105. for i in range(32, 127):
  106.     non_ascii_translate_tab[i] = chr(i)
  107.  
  108. for ch in ' \t\r\n':
  109.     non_ascii_translate_tab[ord(ch)] = ch
  110.  
  111. del i
  112. del ch
  113. non_ascii_translate_tab = ''.join(non_ascii_translate_tab)
  114.  
  115. def crack_content_xyz(msg):
  116.     yield 'content-type:' + msg.get_content_type()
  117.     x = msg.get_param('type')
  118.     if x is not None:
  119.         yield 'content-type/type:' + x.lower()
  120.     
  121.     
  122.     try:
  123.         for x in msg.get_charsets(None):
  124.             if x is not None:
  125.                 yield 'charset:' + x.lower()
  126.                 continue
  127.     except UnicodeEncodeError:
  128.         yield 'charset:invalid_unicode'
  129.  
  130.     x = msg.get('content-disposition')
  131.     if x is not None:
  132.         yield 'content-disposition:' + x.lower()
  133.     
  134.     
  135.     try:
  136.         fname = msg.get_filename()
  137.         if fname is not None:
  138.             for x in crack_filename(fname):
  139.                 yield 'filename:' + x
  140.             
  141.     except TypeError:
  142.         yield 'filename:<bogus>'
  143.  
  144.  
  145. base64_re = re.compile('\n    [ \\t]*\n    [a-zA-Z0-9+/]*\n    (=*)\n    [ \\t]*\n    \\r?\n    \\n\n', re.VERBOSE)
  146.  
  147. def try_to_repair_damaged_base64(text):
  148.     i = 0
  149.     while True:
  150.         m = base64_re.match(text, i)
  151.         if not m:
  152.             break
  153.         
  154.         i = m.end()
  155.         if m.group(1):
  156.             break
  157.             continue
  158.     base64text = ''
  159.     if i:
  160.         base64 = text[:i]
  161.         
  162.         try:
  163.             base64text = binascii.a2b_base64(base64)
  164.         except binascii.Error:
  165.             for j in xrange(len(base64), 1, -1):
  166.                 
  167.                 try:
  168.                     base64text = binascii.a2b_base64(base64[:j])
  169.                 except binascii.Error:
  170.                     e = None
  171.                     continue
  172.  
  173.             
  174.         except:
  175.             pass
  176.         
  177.  
  178.     None<EXCEPTION MATCH>binascii.Error
  179.     return base64text + text[i:]
  180.  
  181.  
  182. def breakdown_host(host):
  183.     parts = host.split('.')
  184.     for i in range(1, len(parts) + 1):
  185.         yield '.'.join(parts[-i:])
  186.     
  187.  
  188.  
  189. def breakdown_ipaddr(ipaddr):
  190.     parts = ipaddr.split('.')
  191.     for i in range(1, 5):
  192.         yield '.'.join(parts[:i])
  193.     
  194.  
  195.  
  196. def log2(n, log = math.log, c = math.log(2)):
  197.     return log(n) / c
  198.  
  199.  
  200. class Stripper(object):
  201.     separator = ''
  202.     
  203.     def __init__(self, find_start, find_end):
  204.         self.find_start = find_start
  205.         self.find_end = find_end
  206.  
  207.     
  208.     def analyze(self, text):
  209.         i = 0
  210.         retained = []
  211.         pushretained = retained.append
  212.         tokens = []
  213.         while True:
  214.             m = self.find_start(text, i)
  215.             if not m:
  216.                 pushretained(text[i:])
  217.                 break
  218.             
  219.             (start, end) = m.span()
  220.             pushretained(text[i:start])
  221.             tokens.extend(self.tokenize(m))
  222.             m = self.find_end(text, end)
  223.             if not m:
  224.                 pushretained(text[start:])
  225.                 break
  226.             
  227.             (dummy, i) = m.span()
  228.         return (self.separator.join(retained), tokens)
  229.  
  230.     
  231.     def tokenize(self, match_object):
  232.         return []
  233.  
  234.  
  235. uuencode_begin_re = re.compile('\n    ^begin \\s+\n    (\\S+) \\s+   # capture mode\n    (\\S+) \\s*   # capture filename\n    $\n', re.VERBOSE | re.MULTILINE)
  236. uuencode_end_re = re.compile('^end\\s*\\n', re.MULTILINE)
  237.  
  238. class UUencodeStripper(Stripper):
  239.     
  240.     def __init__(self):
  241.         Stripper.__init__(self, uuencode_begin_re.search, uuencode_end_re.search)
  242.  
  243.     
  244.     def tokenize(self, m):
  245.         (mode, fname) = m.groups()
  246.         return [] + [ 'uuencode:%s' % x for x in crack_filename(fname) ]
  247.  
  248.  
  249. crack_uuencode = UUencodeStripper().analyze
  250. url_fancy_re = re.compile(' \n    \\b                      # the preceeding character must not be alphanumeric\n    (?: \n        (?:\n            (https? | ftp)  # capture the protocol\n            ://             # skip the boilerplate\n        )|\n        (?= ftp\\.[^\\.\\s<>"\'\\x7f-\\xff] )|  # allow the protocol to be missing, but only if\n        (?= www\\.[^\\.\\s<>"\'\\x7f-\\xff] )   # the rest of the url starts "www.x" or "ftp.x" \n    )\n    # Do a reasonable attempt at detecting the end.  It may or may not\n    # be in HTML, may or may not be in quotes, etc.  If it\'s full of %\n    # escapes, cool -- that\'s a clue too.\n    ([^\\s<>"\'\\x7f-\\xff]+)  # capture the guts\n', re.VERBOSE)
  251. url_re = re.compile('\n    (https? | ftp)  # capture the protocol\n    ://             # skip the boilerplate\n    # Do a reasonable attempt at detecting the end.  It may or may not\n    # be in HTML, may or may not be in quotes, etc.  If it\'s full of %\n    # escapes, cool -- that\'s a clue too.\n    ([^\\s<>"\'\\x7f-\\xff]+)  # capture the guts\n', re.VERBOSE)
  252. urlsep_re = re.compile('[;?:@&=+,$.]')
  253.  
  254. class URLStripper(Stripper):
  255.     
  256.     def __init__(self):
  257.         if options[('Tokenizer', 'x-fancy_url_recognition')]:
  258.             search = url_fancy_re.search
  259.         else:
  260.             search = url_re.search
  261.         Stripper.__init__(self, search, re.compile('').search)
  262.  
  263.     
  264.     def tokenize(self, m):
  265.         (proto, guts) = m.groups()
  266.         if not guts:
  267.             raise AssertionError
  268.         if proto is None:
  269.             if guts.lower().startswith('www'):
  270.                 proto = 'http'
  271.             elif guts.lower().startswith('ftp'):
  272.                 proto = 'ftp'
  273.             else:
  274.                 proto = 'unknown'
  275.         
  276.         tokens = [
  277.             'proto:' + proto]
  278.         pushclue = tokens.append
  279.         if options[('Tokenizer', 'x-pick_apart_urls')]:
  280.             url = proto + '://' + guts
  281.             escapes = re.findall('%..', guts)
  282.             if escapes:
  283.                 pushclue('url:%%%d' % int(log2(len(escapes))))
  284.             
  285.             []([ 'url:' + escape for escape in escapes ])
  286.             url = urllib.unquote(url)
  287.             (scheme, netloc, path, params, query, frag) = urlparse.urlparse(url)
  288.             (user_pwd, host_port) = urllib.splituser(netloc)
  289.             (host, port) = urllib.splitport(host_port)
  290.             if port is not None:
  291.                 if (scheme == 'http' or port != '80' or scheme == 'https') and port != '443':
  292.                     pushclue('url:non-standard %s port' % scheme)
  293.                 
  294.             
  295.             if re.match('(\\d+\\.?){4,4}$', host) is not None:
  296.                 pushclue('url:ip addr')
  297.             
  298.             (proto, guts) = url.split('://', 1)
  299.         
  300.         while guts and guts[-1] in '.:?!/':
  301.             guts = guts[:-1]
  302.         for piece in guts.split('/'):
  303.             for chunk in urlsep_re.split(piece):
  304.                 pushclue('url:' + chunk)
  305.             
  306.         
  307.         return tokens
  308.  
  309.  
  310. received_complaints_re = re.compile('\\([a-z]+(?:\\s+[a-z]+)+\\)')
  311.  
  312. class SlurpingURLStripper(URLStripper):
  313.     
  314.     def __init__(self):
  315.         URLStripper.__init__(self)
  316.  
  317.     
  318.     def analyze(self, text):
  319.         classifier.slurp_wordstream = None
  320.         return URLStripper.analyze(self, text)
  321.  
  322.     
  323.     def tokenize(self, m):
  324.         tokens = URLStripper.tokenize(self, m)
  325.         if not options[('URLRetriever', 'x-slurp_urls')]:
  326.             return tokens
  327.         
  328.         (proto, guts) = m.groups()
  329.         if proto != 'http':
  330.             return tokens
  331.         
  332.         if not guts:
  333.             raise AssertionError
  334.         while guts and guts[-1] in '.:;?!/)':
  335.             guts = guts[:-1]
  336.         classifier.slurp_wordstream = (proto, guts)
  337.         return tokens
  338.  
  339.  
  340. if options[('URLRetriever', 'x-slurp_urls')]:
  341.     crack_urls = SlurpingURLStripper().analyze
  342. else:
  343.     crack_urls = URLStripper().analyze
  344. html_style_start_re = re.compile('\n    < \\s* style\\b [^>]* >\n', re.VERBOSE)
  345.  
  346. class StyleStripper(Stripper):
  347.     
  348.     def __init__(self):
  349.         Stripper.__init__(self, html_style_start_re.search, re.compile('</style>').search)
  350.  
  351.  
  352. crack_html_style = StyleStripper().analyze
  353.  
  354. class CommentStripper(Stripper):
  355.     
  356.     def __init__(self):
  357.         Stripper.__init__(self, re.compile('<!--|<\\s*comment\\s*[^>]*>').search, re.compile('-->|</comment>').search)
  358.  
  359.  
  360. crack_html_comment = CommentStripper().analyze
  361.  
  362. class NoframesStripper(Stripper):
  363.     
  364.     def __init__(self):
  365.         Stripper.__init__(self, re.compile('<\\s*noframes\\s*>').search, re.compile('</noframes\\s*>').search)
  366.  
  367.  
  368. crack_noframes = NoframesStripper().analyze
  369. virus_re = re.compile('\n    < /? \\s* (?: script | iframe) \\b\n|   \\b src= [\'"]? cid:\n|   \\b (?: height | width) = [\'"]? 0\n', re.VERBOSE)
  370.  
  371. def find_html_virus_clues(text):
  372.     for bingo in virus_re.findall(text):
  373.         yield bingo
  374.     
  375.  
  376. numeric_entity_re = re.compile('&#(\\d+);')
  377.  
  378. def numeric_entity_replacer(m):
  379.     
  380.     try:
  381.         return chr(int(m.group(1)))
  382.     except:
  383.         return '?'
  384.  
  385.  
  386. breaking_entity_re = re.compile('\n     \n|   < (?: p\n      |   br\n      )\n    >\n', re.VERBOSE)
  387.  
  388. class Tokenizer:
  389.     date_hms_re = re.compile(' (?P<hour>[0-9][0-9]):(?P<minute>[0-9][0-9])(?::[0-9][0-9])? ')
  390.     date_formats = ('%a, %d %b %Y %H:%M:%S (%Z)', '%a, %d %b %Y %H:%M:%S %Z', '%d %b %Y %H:%M:%S (%Z)', '%d %b %Y %H:%M:%S %Z', '%a, %d %b %Y %H:%M (%Z)', '%a, %d %b %Y %H:%M %Z', '%d %b %Y %H:%M (%Z)', '%d %b %Y %H:%M %Z')
  391.     
  392.     def __init__(self):
  393.         self.setup()
  394.  
  395.     
  396.     def setup(self):
  397.         '''Get the tokenizer ready to use; this should be called after
  398.         all options have been set.'''
  399.         pass
  400.  
  401.     
  402.     def get_message(self, obj):
  403.         return get_message(obj)
  404.  
  405.     
  406.     def tokenize(self, obj):
  407.         msg = self.get_message(obj)
  408.         for tok in self.tokenize_headers(msg):
  409.             yield tok
  410.         
  411.         for tok in self.tokenize_body(msg):
  412.             yield tok
  413.         
  414.  
  415.     
  416.     def tokenize_headers(self, msg):
  417.         for x in msg.walk():
  418.             for w in crack_content_xyz(x):
  419.                 yield w
  420.             
  421.         
  422.         if options[('Tokenizer', 'basic_header_tokenize')]:
  423.             for k, v in msg.items():
  424.                 k = k.lower()
  425.                 for rx in self.basic_skip:
  426.                     if rx.match(k):
  427.                         break
  428.                         continue
  429.                 else:
  430.                     for w in subject_word_re.findall(v):
  431.                         for t in tokenize_word(w):
  432.                             yield '%s:%s' % (k, t)
  433.                         
  434.                     
  435.             
  436.             if options[('Tokenizer', 'basic_header_tokenize_only')]:
  437.                 return None
  438.             
  439.         
  440.         if options[('Tokenizer', 'x-search_for_habeas_headers')]:
  441.             habeas_headers = [
  442.                 ('X-Habeas-SWE-1', 'winter into spring'),
  443.                 ('X-Habeas-SWE-2', 'brightly anticipated'),
  444.                 ('X-Habeas-SWE-3', 'like Habeas SWE (tm)'),
  445.                 ('X-Habeas-SWE-4', 'Copyright 2002 Habeas (tm)'),
  446.                 ('X-Habeas-SWE-5', 'Sender Warranted Email (SWE) (tm). The sender of this'),
  447.                 ('X-Habeas-SWE-6', 'email in exchange for a license for this Habeas'),
  448.                 ('X-Habeas-SWE-7', 'warrant mark warrants that this is a Habeas Compliant'),
  449.                 ('X-Habeas-SWE-8', 'Message (HCM) and not spam. Please report use of this'),
  450.                 ('X-Habeas-SWE-9', 'mark in spam to <http://www.habeas.com/report/>.')]
  451.             valid_habeas = 0
  452.             invalid_habeas = False
  453.             for opt, val in habeas_headers:
  454.                 habeas = msg.get(opt)
  455.                 if habeas is not None:
  456.                     if options[('Tokenizer', 'x-reduce_habeas_headers')]:
  457.                         if habeas == val:
  458.                             valid_habeas += 1
  459.                         else:
  460.                             invalid_habeas = True
  461.                     elif habeas == val:
  462.                         yield opt.lower() + ':valid'
  463.                     else:
  464.                         yield opt.lower() + ':invalid'
  465.                 options[('Tokenizer', 'x-reduce_habeas_headers')]
  466.             
  467.             if options[('Tokenizer', 'x-reduce_habeas_headers')]:
  468.                 if invalid_habeas == True:
  469.                     yield 'x-habeas-swe:invalid'
  470.                 elif valid_habeas == 9:
  471.                     yield 'x-habeas-swe:valid'
  472.                 
  473.             
  474.         
  475.         x = msg.get('subject', '')
  476.         
  477.         try:
  478.             subjcharsetlist = email.Header.decode_header(x)
  479.         except (binascii.Error, email.Errors.HeaderParseError):
  480.             subjcharsetlist = [
  481.                 (x, 'invalid')]
  482.  
  483.         for x, subjcharset in subjcharsetlist:
  484.             if subjcharset is not None:
  485.                 yield 'subjectcharset:' + subjcharset
  486.             
  487.             x = x.replace('\r', ' ')
  488.             for w in subject_word_re.findall(x):
  489.                 for t in tokenize_word(w):
  490.                     yield 'subject:' + t
  491.                 
  492.             
  493.             for w in punctuation_run_re.findall(x):
  494.                 yield 'subject:' + w
  495.             
  496.         
  497.         for field in options[('Tokenizer', 'address_headers')]:
  498.             addrlist = msg.get_all(field, [])
  499.             if not addrlist:
  500.                 yield field + ':none'
  501.                 continue
  502.             
  503.             noname_count = 0
  504.             for name, addr in email.Utils.getaddresses(addrlist):
  505.                 if name:
  506.                     
  507.                     try:
  508.                         subjcharsetlist = email.Header.decode_header(name)
  509.                     except (binascii.Error, email.Errors.HeaderParseError):
  510.                         subjcharsetlist = [
  511.                             (name, 'invalid')]
  512.  
  513.                     for name, charset in subjcharsetlist:
  514.                         yield '%s:name:%s' % (field, name.lower())
  515.                         if charset is not None:
  516.                             yield '%s:charset:%s' % (field, charset)
  517.                             continue
  518.                     
  519.                 else:
  520.                     noname_count += 1
  521.                 if addr:
  522.                     for w in addr.lower().split('@'):
  523.                         yield '%s:addr:%s' % (field, w)
  524.                     
  525.                 yield field + ':addr:none'
  526.             
  527.             if noname_count:
  528.                 yield '%s:no real name:2**%d' % (field, round(log2(noname_count)))
  529.                 continue
  530.         
  531.         if options[('Tokenizer', 'summarize_email_prefixes')]:
  532.             all_addrs = []
  533.             addresses = msg.get_all('to', []) + msg.get_all('cc', [])
  534.             for name, addr in email.Utils.getaddresses(addresses):
  535.                 all_addrs.append(addr.lower())
  536.             
  537.             if len(all_addrs) > 1:
  538.                 pfx = os.path.commonprefix(all_addrs)
  539.                 if pfx:
  540.                     score = len(pfx) * len(all_addrs) // 10
  541.                     if score > 3:
  542.                         yield 'pfxlen:big'
  543.                     else:
  544.                         yield 'pfxlen:%d' % score
  545.                 
  546.             
  547.         
  548.         if options[('Tokenizer', 'summarize_email_suffixes')]:
  549.             all_addrs = []
  550.             addresses = msg.get_all('to', []) + msg.get_all('cc', [])
  551.             for name, addr in email.Utils.getaddresses(addresses):
  552.                 addr = list(addr)
  553.                 addr.reverse()
  554.                 addr = ''.join(addr)
  555.                 all_addrs.append(addr.lower())
  556.             
  557.             if len(all_addrs) > 1:
  558.                 sfx = os.path.commonprefix(all_addrs)
  559.                 if sfx:
  560.                     score = len(sfx) * len(all_addrs) // 10
  561.                     if score > 5:
  562.                         yield 'sfxlen:big'
  563.                     else:
  564.                         yield 'sfxlen:%d' % score
  565.                 
  566.             
  567.         
  568.         for field in ('to', 'cc'):
  569.             count = 0
  570.             for addrs in msg.get_all(field, []):
  571.                 count += len(addrs.split(','))
  572.             
  573.             if count > 0:
  574.                 yield '%s:2**%d' % (field, round(log2(count)))
  575.                 continue
  576.         
  577.         for field in ('x-mailer',):
  578.             prefix = field + ':'
  579.             x = msg.get(field, 'none').lower()
  580.             yield prefix + ' '.join(x.split())
  581.         
  582.         if options[('Tokenizer', 'mine_received_headers')]:
  583.             for header in msg.get_all('received', ()):
  584.                 header = ' '.join(header.split()).lower()
  585.                 for clue in received_complaints_re.findall(header):
  586.                     yield 'received:' + clue
  587.                 
  588.                 for pat, breakdown in [
  589.                     (received_host_re, breakdown_host),
  590.                     (received_ip_re, breakdown_ipaddr)]:
  591.                     m = pat.search(header)
  592.                     if m:
  593.                         for tok in breakdown(m.group(1)):
  594.                             yield 'received:' + tok
  595.                         
  596.                 
  597.             
  598.         
  599.         msgid = msg.get('message-id', '')
  600.         m = message_id_re.match(msgid)
  601.         if m:
  602.             yield 'message-id:@%s' % m.group(1)
  603.         else:
  604.             yield 'message-id:invalid'
  605.         x2n = { }
  606.         if options[('Tokenizer', 'count_all_header_lines')]:
  607.             for x in msg.keys():
  608.                 x2n[x] = x2n.get(x, 0) + 1
  609.             
  610.         else:
  611.             safe_headers = options[('Tokenizer', 'safe_headers')]
  612.             for x in msg.keys():
  613.                 if x.lower() in safe_headers:
  614.                     x2n[x] = x2n.get(x, 0) + 1
  615.                     continue
  616.             
  617.         for x in x2n.items():
  618.             yield 'header:%s:%d' % x
  619.         
  620.         if options[('Tokenizer', 'record_header_absence')]:
  621.             for k in x2n:
  622.                 if k.lower() not in options[('Tokenizer', 'safe_headers')]:
  623.                     yield 'noheader:' + k
  624.                     continue
  625.             
  626.         
  627.  
  628.     
  629.     def tokenize_body(self, msg, maxword = options[('Tokenizer', 'skip_max_word_size')]):
  630.         """Generate a stream of tokens from an email Message.
  631.  
  632.         If options['Tokenizer', 'check_octets'] is True, the first few
  633.         undecoded characters of application/octet-stream parts of the
  634.         message body become tokens.
  635.         """
  636.         if options[('Tokenizer', 'check_octets')]:
  637.             for part in octetparts(msg):
  638.                 
  639.                 try:
  640.                     text = part.get_payload(decode = True)
  641.                 except:
  642.                     yield "control: couldn't decode octet"
  643.                     text = part.get_payload(decode = False)
  644.  
  645.                 if text is None:
  646.                     yield 'control: octet payload is None'
  647.                     continue
  648.                 
  649.                 yield 'octet:%s' % text[:options[('Tokenizer', 'octet_prefix_size')]]
  650.             
  651.         
  652.         for part in textparts(msg):
  653.             
  654.             try:
  655.                 text = part.get_payload(decode = True)
  656.             except:
  657.                 yield "control: couldn't decode"
  658.                 text = part.get_payload(decode = False)
  659.                 if text is not None:
  660.                     text = try_to_repair_damaged_base64(text)
  661.                 
  662.  
  663.             if text is None:
  664.                 yield 'control: payload is None'
  665.                 continue
  666.             
  667.             text = numeric_entity_re.sub(numeric_entity_replacer, text)
  668.             text = text.lower()
  669.             if options[('Tokenizer', 'replace_nonascii_chars')]:
  670.                 text = text.translate(non_ascii_translate_tab)
  671.             
  672.             for t in find_html_virus_clues(text):
  673.                 yield 'virus:%s' % t
  674.             
  675.             for cracker in (crack_uuencode, crack_urls, crack_html_style, crack_html_comment, crack_noframes):
  676.                 (text, tokens) = cracker(text)
  677.                 for t in tokens:
  678.                     yield t
  679.                 
  680.             
  681.             text = breaking_entity_re.sub(' ', text)
  682.             text = html_re.sub('', text)
  683.             for w in text.split():
  684.                 n = len(w)
  685.                 if n <= n:
  686.                     pass
  687.                 elif n <= maxword:
  688.                     yield w
  689.                     continue
  690.                 if n >= 3:
  691.                     for t in tokenize_word(w):
  692.                         yield t
  693.                     
  694.             
  695.         
  696.  
  697.  
  698. global_tokenizer = Tokenizer()
  699. tokenize = global_tokenizer.tokenize
  700.